﻿//PlazaRevisionManager.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Plaza
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;
	using System.Threading;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Serialization;
	using RevisionInfo = Ecng.Common.RefPair<int, System.Collections.Generic.IDictionary<string, long>>;

	using StockSharp.Plaza.Metadata;

	/// <summary>
	/// Менеджер управления ревизиями потоков <see cref="PlazaStreamManager"/>.
	/// </summary>
	public class PlazaRevisionManager : Disposable
	{
		private readonly Action<Exception> _errorHandler;
		private Timer _timer;
		private readonly SynchronizedDictionary<PlazaStream, RevisionInfo> _changes = new SynchronizedDictionary<PlazaStream, RevisionInfo>();
		private readonly SynchronizedDictionary<PlazaStream, RevisionInfo> _revisions = new SynchronizedDictionary<PlazaStream, RevisionInfo>();
		private int _saveCount;
		private const int _maxSaveCount = 100;

		private readonly SynchronizedDictionary<PlazaStream, PlazaTable[]> _tables = new SynchronizedDictionary<PlazaStream, PlazaTable[]>(); 

		internal PlazaRevisionManager(Action<Exception> errorHandler)
		{
			if (errorHandler == null)
				throw new ArgumentNullException("errorHandler");

			_errorHandler = errorHandler;
			Tables = new SynchronizedSet<PlazaTable>();
		}

        /// <summary>
		/// Таблицы, которые необходимо отслеживать на изменение ревизий.
        /// </summary>
		public ISynchronizedCollection<PlazaTable> Tables { get; private set; }

		private string _revisionPath = @"StockSharp_Plaza\Revisions";

		/// <summary>
		/// Путь к директории, куда будут сохраняться ревизии.
		/// </summary>
		/// <remarks>
		/// По-умолчанию путь равен директории \StockSharp_Plaza\Revisions, которая будет создана в локальной директории.
		/// </remarks>
		public string RevisionPath
		{
			get { return _revisionPath; }
			set
			{
				if (value.IsEmpty())
					throw new ArgumentNullException("value");

				_revisionPath = value;
			}
		}

		private TimeSpan _interval = TimeSpan.FromSeconds(10);

		/// <summary>
		/// Интервал сохранения измененных ревизий. По-умолчанию равно 10 секундам.
		/// </summary>
		/// <remarks>Если значение установлено в <see cref="TimeSpan.Zero"/>,
		/// то таймер сохранения ревизий в файл выключен,
		/// и необходимо самостоятельно вызывать метод <see cref="SaveRevisions()"/>.</remarks>
		public TimeSpan Interval
		{
			get { return _interval; }
			set
			{
				if (value < TimeSpan.Zero)
					throw new ArgumentOutOfRangeException("value");

				_interval = value;
			}
		}

		/// <summary>
		/// Сохранить ревизии в файл.
		/// </summary>
		public void SaveRevisions()
		{
			var changes = _changes.SyncGet(d => d.CopyAndClear());

			if (changes.IsEmpty())
				return;

			try
			{
				var serializer = new XmlSerializer<RevisionInfo>();

				foreach (var pair in changes)
					serializer.Serialize(pair.Value, GetFileName(pair.Key));
			}
			catch (Exception ex)
			{
				_errorHandler(ex);
			}
		}

		private string GetFileName(PlazaStream stream)
		{
			return Path.Combine(RevisionPath, stream.Name + ".xml");
		}

		internal void Start(IDictionary<PlazaStream, PlazaTable[]> tables)
		{
			_tables.Clear();
			_tables.AddRange(Tables.SyncGet(c => c.GroupBy(t => t.Stream).ToDictionary(g => g.Key, g => g.ToArray())));

			foreach (var pair in tables)
			{
				var stream = pair.Key;
				var streamTables = _tables.TryGetValue(stream);

				if (streamTables == null)
					continue;

				streamTables = streamTables.Intersect(pair.Value).ToArray();

				if (streamTables.IsEmpty())
					_tables.Remove(stream);
				else
					_tables[stream] = streamTables;
			}

			Directory.CreateDirectory(RevisionPath);

			if (Interval > TimeSpan.Zero)
			{
				_timer = ThreadingHelper
							.Timer(SaveRevisions)
							.Interval(Interval);
			}
		}

		internal void Stop()
		{
			try
			{
				if (_timer == null)
					return;

				_timer.Dispose();
				_timer = null;
			}
			finally
			{
				SaveRevisions();
			}
		}

		internal void LoadState(PlazaStream stream)
		{
			if (stream == null)
				throw new ArgumentNullException("stream");

			var tables = _tables.TryGetValue(stream);

			if (tables == null)
				return;

			var revisions = _revisions.TryGetValue(stream);

			if (revisions == null)
			{
				var fileName = GetFileName(stream);

				try
				{
					if (File.Exists(fileName))
						revisions = new XmlSerializer<RevisionInfo>().Deserialize(fileName);
				}
				catch
				{
					File.Delete(fileName);
				}

				if (revisions == null)
					return;
			}

			stream.LifeNum = revisions.First;

			foreach (var table in tables)
			{
				var revision = revisions.Second.TryGetValue2(table.Name);

				if (revision != null)
					table.Revision = (long)revision;
			}
		}

		internal void SaveState(PlazaStream stream)
		{
			if (stream == null)
				throw new ArgumentNullException("stream");

			var tables = _tables.TryGetValue(stream);

			if (tables == null)
				return;

			if (_saveCount < _maxSaveCount)
			{
				_saveCount++;
				return;
			}

			_saveCount = 0;
			_revisions[stream] = _changes[stream] = new RevisionInfo(stream.LifeNum, tables.ToDictionary(t => t.Name, t => t.Revision));
		}

		/// <summary>
		/// Освободить занятые ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			Stop();
			base.DisposeManaged();
		}
	}
}
